// Copyright 2011 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Invoke the actions specified by a user, along with any actions they depend
 * upon that are not up to date.
 *
 * @param {!Array.<string>} actionNames
 */
flame.internal.processUserRequest = function(actionNames) {
  var fi = flame.internal;

  // Add all of the actions to our queue of work to perform when appropriate
  // actions are resolved.
  flame.resolveActions(actionNames, function(actions) {});

  // Resolve actions and call the appropriate callbacks until we're out of
  // actions to resolve.
  fi.processResolveWork();

  // Build a graph of input and output file dependencies.
  var fileGraph = fi.buildFileGraph(fi.outputFileIndex);

  // Get the nodes in the subgraph composed of all of the files depended upon by
  // the files output by the user-requested actions.
  var relevantNodes = new fi.StringSet;
  actionNames.forEach(function(actionName) {
    var action = fi.registeredActions.get(actionName);
    action.outputs.forEach(function(output) {
      fileGraph.addReachableNodes(output, relevantNodes);
    });
  });

  // Sort the file work topologically.
  var sortedWork = relevantNodes.getElements();
  fileGraph.sortTopologically(sortedWork);

  // Make sure our build product directory exists.
  if (fi.invokeCommand('mkdir -p .flame-build').exitCode != 0) {
    throw new Error('Couldn\'t create a flame-build directory.');
  }

  // A utility function for below.
  //
  // TODO(jacobsa): Split out this and similar functions below into a file
  // system utilities file, with tests.
  function fileExists(path) {
    return fi.invokeCommand(fi.sprintf('ls %s', path)).exitCode == 0;
  }

  // Given a path to a file, return the number of directories deep it is. For
  // example, 'foo/bar.txt' is one directory deep.
  function directoryDepth(path) {
    return path.split('/').length - 1;
  }

  // Return the path to the Nth ancestor of the current directory.
  function nthAncestorPath(n) {
    var result = '';
    for (var i = 0; i < n; ++i) {
      result = '../' + result;
    }

    return result;
  }

  // Invoke appropriate actions one by one until we get to the end of our work.
  // Make sure not to invoke actions that have already been invoked.
  var actionsRun = new fi.StringSet;
  sortedWork.forEach(function(file) {
    fi.writeToStdout(fi.sprintf('Processing file: %s\n', file));

    // If there's an action that will create this file and it hasn't yet been
    // run, run it.
    if (fi.outputFileIndex.contains(file)) {
      var action = fi.outputFileIndex.get(file);
      if (!actionsRun.contains(action.name)) {
        // The action's inputs have already been processed earlier in the
        // sortedWork loop, so they should exist. Invoke each of its commands.
        action.commands.forEach(function(command) {
          fi.writeToStdout(fi.sprintf('    Invoking: %s\n', command));

          var fullCommand = fi.sprintf('cd .flame-build && %s', command);
          if (fi.invokeCommand(fullCommand).exitCode != 0) {
            throw new Error('Error from command.');
          }
        });

        // The action should have created every file it promised to.
        action.outputs.forEach(function(output) {
          if (!fileExists(fi.sprintf('.flame-build/%s', output))) {
            throw new Error(
                fi.sprintf('Action didn\'t create output: %s', output));
          }
        });

        // Record that we've already run this action.
        actionsRun.insert(action.name);
      }
    } else {
      // There's no action for this file. It should exist in the user's
      // workspace.
      if (!fileExists(file)) {
        throw new Error(fi.sprintf('Don\'t know how to build: %s', file));
      }

      // Make a symlink for the flame-build directory.
      var linkCommand =
          fi.sprintf(
              'mkdir -p .flame-build/`dirname %s` && ' +
                  'ln -f -s %s/%s .flame-build/%s',
              file,
              nthAncestorPath(directoryDepth(file) + 1),
              file,
              file);

      if (fi.invokeCommand(linkCommand).exitCode != 0) {
        throw new Error('Couldn\'t create symlink.');
      }
    }
  });
};
